#ifndef SIMPLE_SUPPORT_ALGORITHM_TRAITS_HPP
#define SIMPLE_SUPPORT_ALGORITHM_TRAITS_HPP

#include <iterator> // std::begin std::end
#include <type_traits> // std::true_type std::false_type
#include <utility> // std::declval
#include <cstddef> // std::nullptr_t

// TODO: could do much better here with requires
namespace simple::support
{

	template<typename Type, typename = std::nullptr_t>
	struct is_iterable : public std::false_type {};

	template<typename Type>
	struct is_iterable<Type,
	decltype(
		// not (std::declval<Type>() != std::declval<Type>()), // output iterators are not comparable
		void(*std::declval<Type>()),
		void(++(std::declval<Type&>())),
		nullptr)>
	: public std::true_type
	{};

	template<typename Type>
	constexpr bool is_iterable_v = is_iterable<Type>::value;

	template <typename T>
	concept iterable = is_iterable_v<T>;

	template<typename T1, typename T2, typename = std::nullptr_t>
	struct is_equality_comparable : public std::false_type {};

	template<typename T1, typename T2>
	struct is_equality_comparable<T1, T2,
	decltype(
		not (std::declval<T1>() != std::declval<T2>()),
		not (std::declval<T1>() == std::declval<T2>()),
		not (std::declval<T2>() != std::declval<T1>()),
		not (std::declval<T2>() == std::declval<T1>()),
		nullptr)>
	: public std::true_type
	{};

	template<typename T1, typename T2>
	constexpr bool is_equality_comparable_v = is_equality_comparable<T1,T2>::value;

	// TODO use std
	template<typename T1, typename T2>
	concept equality_comparable = is_equality_comparable_v<T1,T2>;

	namespace is_range_helper
	{

		using std::begin;
		using std::end;

		template <typename T, typename = std::nullptr_t>
		struct check
		: public std::false_type {};
		template <typename T>
		struct check<T, decltype(
			not (begin(std::declval<T>()) != end(std::declval<T>())),
			void(*begin(std::declval<T>())),
			void(++std::declval<decltype(begin(std::declval<T>()))&>()),
			nullptr)>
		: public std::true_type {};

	} // namespace is_range_helper

	// TODO: rename
	// range is much stronger IMHO, excludes sentinels, implies distance
	// or maybe switch to using sequence from reaches.hpp for good
	template <typename T, typename = std::nullptr_t>
	struct is_range : public is_range_helper::check<T> {};
	template <typename T>
	constexpr auto is_range_v = is_range<T>::value;

} // namespace simple::support

#endif /* end of include guard */
